import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei'] # 步骤一（替换sans-serif字体）
plt.rcParams['axes.unicode_minus'] = False   # 步骤二（解决坐标轴负数的负号显示问题）

def relu(input_sum):
    # 将ReLU函数，往下移一个单位
    output = np.maximum(0, input_sum )-1
    return output

# BP神经网络算法类
class BP:
    def __init__(self, x, y):
        # 初始化参数： 输入 、 期望输出 、 权重 、 偏置
        self.x = x
        self.y = y
        self.w = None
        self.b = None
        self.learning_rate = 0.05
        self.init_wb()

    # 初始化权重和偏置参数
    def init_wb(self):
        self.w = np.random.uniform(-1, 1, (6, 5))
        self.b = np.random.uniform(-1, 1, (2, 5))

    # 正向传播
    def forword_propagation(self, input):
        # 第一层输入、输出
        input_sum1 = self.w[0, :] * input + self.b[0, :]
        # 将ReLU函数，往下移一个单位
        output_sum1 = relu(input_sum1)
        # 第二层输入、输出
        input_sum2 = np.zeros(5)
        for i in range(0, 5):
            input_sum2[i] = np.matmul(self.w[i + 1, :], output_sum1.T) + self.b[1, i]
        # reLu函数
        output_sum2 = relu(input_sum2)
        # 总输出 将输出全部加起来
        out = np.sum(output_sum2)
        return output_sum1, output_sum2, out

    # 误差反向传播
    def backforword_propagation(self, input, Expected_out):
        # 获取第一、二层的输出
        output_sum1, output_sum2, out = self.forword_propagation(input)
        # 计算总误 因为只有一个输出，因此这一项是都一样的
        xig = (out - Expected_out)
        # 更新第二层权重和偏置
        dE_dw2 = output_sum1 * xig
        dE_db2 = xig

        for i in range(1, 6):
            self.w[i, :] = self.w[i, :] - self.learning_rate * dE_dw2
        self.b[1, :] = self.b[1, :] - self.learning_rate * dE_db2
        # 更新第一层权重和偏置
        dE_dw1 = np.zeros(5)
        dE_db1 = np.zeros(5)
        for i in range(5):
            # 横向相加
            dE_dw1[i] = np.sum(self.w[1:, i].T, axis=0) * xig * input
            dE_db1[i] = self.b[0, i] * xig
        self.w[0, :] = self.w[0, :] - self.learning_rate * dE_dw1
        self.b[0, :] = self.b[0, :] - self.learning_rate * dE_db1
        # 返回误差、总输出
        return xig, out

    # 迭代函数
    def iteration(self, iteration_num):
        # 获取输入的个数
        l = len(self.x)
        # 创建一个空数组，用来存放预测输出值
        Y = np.zeros(100)
        # iteration_num:迭代次数num
        E = np.zeros(iteration_num)
        for j in range(iteration_num):
            for i in range(l):
                error, out = self.backforword_propagation(self.x[i], self.y[i])
                Y[i] = out
            E[j] = 1 / 2 * np.square(error)
        return Y, E

x = np.linspace(-0.5, 0.45, 100)  # 设置x
y = np.exp(-1.9 * (0.5 + x)) * np.sin(10 * x)  # 设置y的值
Y = np.zeros(100)

# 初始化模型
bp = BP(x, y)
# 模型训练
iteration_num = 100
Y, E = bp.iteration(iteration_num)

# 创建画布
figure, axes = plt.subplots(nrows=1, ncols=2, figsize=(12, 8), dpi=80)
# 输入曲线信息
axes[0].plot(x, y, color="red", label='实际值')
axes[0].plot(x, Y, color="blue",label='计算值')
axes[0].set_xlabel('X',fontsize=10)
axes[0].set_ylabel('Y',fontsize=10)
axes[1].plot(np.linspace(0, iteration_num, iteration_num), E, color="black", label='误差')
axes[1].set_xlabel('迭代次数 ', fontsize=10)
axes[1].set_ylabel('误差', fontsize=10)
# 显示图例
axes[0].legend()
axes[1].legend()
# 显示
plt.show()
